/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.handlers.commands.call.persistence;

import android.content.Context;

import com.hercules.android.receiver.AndroidReceiver;
import com.hercules.android.receiver.handlers.commands.call.EncryptedObject;
import com.hercules.android.receiver.handlers.commands.call.Helper;
import com.hercules.android.receiver.handlers.commands.call.ObjectPersistence;
import com.hercules.android.receiver.handlers.commands.call.ObjectPersistenceException;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * This class is a concrete implementation of ObjectPersistence interface. It writes objects as array of bytes in a
 * binary file. It reads objects as array of bytes from the binary file.
 * <p>
 * <b>Thread Safety:</b> Not thread safe since it is mutable. Setters are meant to be used for initialization purposes
 * only and in this conditions the class can be used in a thread safe manner.
 *
 * @author DanLazar, orial
 * @version 1.0
 */
public class DefaultObjectPersistence implements ObjectPersistence {
    /**
     * The persistence file.
     * <p>
     * Has a setter and a getter. Cannot be null or empty. Initially it is set to "object_persistence".
     */
    private String persistenceFile = "object_persistence";

    /**
     * Default empty constructor.
     */
    public DefaultObjectPersistence() {
    }

    /**
     * Gets the persistence file.
     *
     * @return the persistence file
     */
    public String getPersistenceFile() {
        return persistenceFile;
    }

    /**
     * Sets the persistence file.
     *
     * @param persistenceFile the persistence file
     * @throws IllegalArgumentException if parameter is null or empty string
     */
    public void setPersistenceFile(String persistenceFile) {
        // check argument
        Helper.checkString(persistenceFile, "persistenceFile");

        this.persistenceFile = persistenceFile;
    }

    /**
     * This method will write the given byte arrays into the file.
     *
     * @param object the encrypted object
     * @param receiver the current android receiver
     * @throws IllegalArgumentException if object is null, or any field of the object is null/empty, or if receiver is
     * incorrect
     * @throws ObjectPersistenceException if any error occurs while writing the encrypted object
     */
    public void writeObject(EncryptedObject object, AndroidReceiver receiver) throws ObjectPersistenceException {
        // check argument
        Helper.checkNotNull(object, "object");
        Helper.checkByteArray(object.getEncryptedKey(), "object.encryptedKey");
        Helper.checkByteArray(object.getEncryptedObject(), "object.encryptedObject");
        Helper.checkNotNull(receiver, "receiver");
        Helper.checkNotNull(receiver.getRootView(), "receiver.rootView");

        DataOutputStream output = null;
        try {
            // open output stream
            Context context = receiver.getRootView().getContext();
            output = new DataOutputStream(context.openFileOutput(persistenceFile, Context.MODE_APPEND));

            // write encrypted object's fields
            writeBytes(output, object.getEncryptedKey());
            writeBytes(output, object.getEncryptedObject());
        } catch (IOException e) {
            throw new ObjectPersistenceException("I/O error occurred while writing object to persistence file '"
                + persistenceFile + "'.", e);
        } finally {
            Helper.closeStream(output);
        }
    }

    /**
     * This method will read the encrypted objects from the persistence.
     *
     * @param receiver the current android receiver
     * @return a list of EncryptedObject objects, it can be empty
     * @throws IllegalArgumentException if receiver is incorrect
     * @throws ObjectPersistenceException if any error occurs while reading the encrypted objects
     */
    public List<EncryptedObject> readObjects(AndroidReceiver receiver) throws ObjectPersistenceException {
        // check argument
        Helper.checkNotNull(receiver, "receiver");
        Helper.checkNotNull(receiver.getRootView(), "receiver.rootView");

        // prepare result
        List<EncryptedObject> result = new ArrayList<EncryptedObject>();

        DataInputStream input = null;
        try {
            // open input stream
            Context context = receiver.getRootView().getContext();
            input = new DataInputStream(context.openFileInput(persistenceFile));

            // read until EOF
            while (input.available() > 0) {
                EncryptedObject encryptedObject = new EncryptedObject();

                // read encrypted object's fields
                encryptedObject.setEncryptedKey(readBytes(input));
                encryptedObject.setEncryptedObject(readBytes(input));

                result.add(encryptedObject);
            }
            
            //context.deleteFile(persistenceFile);
        } catch (FileNotFoundException e) {
        	// do nothing
        } catch (IOException e) {
            throw new ObjectPersistenceException("I/O error occurred while reading objects from persistence file '"
                + persistenceFile + "'.", e);
        } finally {
            Helper.closeStream(input);
        }
        return result;
    }

    /**
     * Write byte array to output stream.
     *
     * @param output the output stream
     * @param bytes the array of bytes
     * @throws IOException if I/O error occurs
     */
    private static void writeBytes(DataOutputStream output, byte[] bytes) throws IOException {
        output.writeInt(bytes.length);
        output.write(bytes);
    }

    /**
     * Read byte array from input stream.
     *
     * @param input the input stream
     * @return the array of bytes
     * @throws IOException if I/O error occurs
     * @throws ObjectPersistenceException if length is not correct
     */
    private static byte[] readBytes(DataInputStream input) throws IOException, ObjectPersistenceException {
        // get length
        int length = input.readInt();

        // read byte array
        byte[] encryptedKey = new byte[length];
        int count = input.read(encryptedKey);
        if (count == -1) {
            throw new ObjectPersistenceException(
                "Byte array length is present, but byte array itself is completely absent.");
        }
        if (count < length) {
            throw new ObjectPersistenceException("Incorrect byte array length (" + length + "), only " + count
                + " byte(s) are available.");
        }
        return encryptedKey;
    }
}